package com.irdstudio.basic.sequence.service.impl.support.redis;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;

/**
 * @description:基于Redis的序列号生成器
 * @date:2019年2月14日 上午9:03:10
 * @author:hengyh
 */

public class RedisSequenceGenerator {
	public static final String REDIS_SEQ_KEY_PREFIX = "SEQUENCE:";
	private static final String REDIS_KEY="REDIS_SEQUENCE";
    private StringRedisTemplate template;

    private RedisLockCluster redisLockCluster;

    public RedisSequenceGenerator(StringRedisTemplate template, RedisLockCluster redisLockCluster) {
        this.template = template;
        this.redisLockCluster = redisLockCluster;
    }

    private Map<String, RedisAtomicLong> valueCache = new ConcurrentHashMap<>(16);

    public Long getRedisSequence(String cls, Long maxValue) {
    	String key = REDIS_SEQ_KEY_PREFIX + cls;
        RedisAtomicLong cached = valueCache.get(key);
        if (cached == null) {
            valueCache.putIfAbsent(key, new RedisAtomicLong(key, template.getConnectionFactory()));
            cached = valueCache.get(key);
        }
        while (true) {
            long cur = cached.getAndIncrement();
            if (cur <= maxValue - maxValue / 10) {
                return cur;
            } else {
                long timeStamp = System.currentTimeMillis();
                Boolean b = redisLockCluster.tryGetDistributedLock(REDIS_KEY, String.valueOf(timeStamp), 4000);
                if (b == true) {
                    if (cached.get() >= maxValue - maxValue / 10) {
                        cached.set(0);
                    }
                    if (template.opsForValue().get(REDIS_KEY).equals(String.valueOf(timeStamp))) {
                        redisLockCluster.releaseDistributedLock(REDIS_KEY);
                    }
                }
            }
        }
    }

    public void afterPropertiesSet() throws Exception {
        final int threadNum = 2;
        CountDownLatch latch = new CountDownLatch(threadNum + 1);
        Runnable task = () -> {
            try {
                latch.countDown();
                latch.await();
                System.out.println(getRedisSequence("test", 1000L));
            } catch (Exception ignore) {}
        };
        Thread[] threads = new Thread[threadNum];
        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }
        latch.countDown();
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println(getRedisSequence("test", 1000L));
    }

    public void setTemplate(StringRedisTemplate template) {
        this.template = template;
    }
}
